package system

import (
	"server/common/dto/response"
	"server/global"
	"time"

	"server/model"
	"server/repo"
	serviceRequest "server/service/dto/request"
	serviceResponse "server/service/dto/response"
	"server/utils"
)

type UserService struct {
	Repository         *repo.Repository[model.User]
	MenuRepository     *repo.Repository[model.Menu]
	UserRoleRepository *repo.Repository[model.UserRole]
	RoleMenuRepository *repo.Repository[model.RoleMenu]
	CaptchaService     CaptchaService
}

func NewUserServiceApp(
	repository *repo.Repository[model.User],
	menurepository *repo.Repository[model.Menu],
	userRepository *repo.Repository[model.UserRole],
	roleRepository *repo.Repository[model.RoleMenu],
) *UserService {
	return &UserService{
		Repository:         repository,
		MenuRepository:     menurepository,
		UserRoleRepository: userRepository,
		RoleMenuRepository: roleRepository,
		CaptchaService:     *NewCaptchaServiceApp(),
	}
}

func userNotFoundHandler() *response.ServiceError {
	return response.WrapError(response.UserNotFoundCode, response.GetStatusCode(response.UserNotFoundCode).Message)
}

func userExistsHandler() *response.ServiceError {
	return response.WrapError(response.UserAccountExistsCode, response.GetStatusCode(response.UserAccountExistsCode).Message)
}

func (us *UserService) CreateUserService(requestData *serviceRequest.CreateUserRequest) *response.ServiceError {
	condition := repo.BuildQueryCondition(map[string]any{
		"account": requestData.Account,
	})
	password, _ := utils.GeneratePassword(requestData.Password)
	user := model.User{
		Account:  requestData.Account,
		Password: password,
	}
	err := us.Repository.GenericCreateWithData(condition, &user, "OR", userExistsHandler)
	return err
}

func (us *UserService) DeleteUserInfoService(id string) (bool, *response.ServiceError) {
	user, err := us.Repository.GenericQueryDetailById(id, userNotFoundHandler)
	if err != nil {
		return false, err
	}
	if user.Account == "admin" {
		return false, response.WrapError(
			response.SystemPermissionCode,
			response.GetStatusCode(response.SystemPermissionCode).Message,
		)
	}
	return us.Repository.GenericLogicDeleteById(id, "", userNotFoundHandler)
}

func (us *UserService) ModifyUserService(requestData *serviceRequest.ModifyUserRequest) *response.ServiceError {
	_, err := us.Repository.GenericQueryDetailById(requestData.ID, userNotFoundHandler)
	if err != nil {
		return err
	}

	success, err := us.Repository.GenericModifyByCondition(requestData.ID, &requestData, userNotFoundHandler)
	if err != nil {
		return err
	}
	if !success {
		return response.WrapError(response.UserModifyInfoFailedCode, response.GetStatusCode(response.UserModifyInfoFailedCode).Message)
	}
	return nil
}

func (us *UserService) GetUserInfoService(id string) (*serviceResponse.UserResponse, *response.ServiceError) {
	var userResponse serviceResponse.UserResponse
	user, err := us.Repository.GenericQueryDetailById(id, userNotFoundHandler)
	if err != nil {
		return nil, err
	}
	if err := utils.ModelToStruct(&userResponse, &user); err != nil {
		return nil, err
	}
	return &userResponse, nil
}

func (us *UserService) GetCurrentUserInfoService(userId string) (*serviceResponse.UserResponse, *response.ServiceError) {
	var userResponse serviceResponse.UserResponse
	condition := map[string]interface{}{
		"user_id": userId,
	}
	user, err := us.Repository.GenericQueryDetailByContition(condition, userNotFoundHandler)
	if err != nil {
		return nil, err
	}
	if err := utils.ModelToStruct(&userResponse, &user); err != nil {
		return nil, err
	}
	return &userResponse, nil
}

func (us *UserService) GetUserPageService(requestData *serviceRequest.UserQueryRequest) (*response.PageResponse[serviceResponse.UserResponse], *response.ServiceError) {
	var paginationResponse response.PageResponse[serviceResponse.UserResponse]
	condition := map[string]interface{}{
		"account": requestData.Account,
	}

	pageData, users, err := us.Repository.GenericQueryPageByCondition(requestData.Page, requestData.Size, requestData.OrderBy, requestData.OrderType, condition)
	if err != nil {
		return nil, err
	}

	var userResponse serviceResponse.UserResponse
	for _, user := range users {
		err := utils.ModelToStruct(&userResponse, &user)
		if err != nil {
			return nil, err
		}
		paginationResponse.List = append(paginationResponse.List, userResponse)
	}
	paginationResponse.Page = pageData.Page
	paginationResponse.Size = pageData.Size
	paginationResponse.Total = pageData.Total
	return &paginationResponse, nil
}

func (us *UserService) GetUserListService(requestData *serviceRequest.UserQueryRequest) (*[]serviceResponse.UserResponse, *response.ServiceError) {
	condition, err := utils.StructToMapWithoutZero(&requestData)
	if err != nil {
		return nil, err
	}
	users, err := us.Repository.GenericQueryListByCondition("", "", condition)
	if err != nil {
		return nil, err
	}
	var userListResponse []serviceResponse.UserResponse
	var userResponse serviceResponse.UserResponse
	for _, user := range users {
		if err := utils.ModelToStruct(&userResponse, &user); err != nil {
			return nil, err
		}
		userListResponse = append(userListResponse, userResponse)
	}
	return &userListResponse, nil
}

func (us *UserService) GetUserMenusService(userId string) (*[]serviceResponse.MenuResponse, *response.ServiceError) {
	_, err := us.GetCurrentUserInfoService(userId)
	if err != nil {
		return nil, err
	}

	var roleIds []string
	condition := map[string]interface{}{
		"user_id": userId,
	}
	userRoles, err := us.UserRoleRepository.GenericQueryListByCondition("", "", condition)
	if err != nil {
		return nil, err
	}

	for _, userRole := range userRoles {
		roleIds = append(roleIds, userRole.RoleID)
	}
	condition = map[string]interface{}{
		"role_id": roleIds,
	}
	roleMenus, err := us.RoleMenuRepository.GenericQueryListByCondition("", "", condition)
	if err != nil {
		return nil, err
	}

	var menuIds []string
	for _, roleMenu := range roleMenus {
		menuIds = append(menuIds, roleMenu.MenuID)
	}

	condition = map[string]interface{}{
		"id": menuIds,
	}
	menus, err := us.MenuRepository.GenericQueryListByCondition("ASC", "sort", condition)
	if err != nil {
		return nil, err
	}

	var menuResponseList []serviceResponse.MenuResponse
	for _, menu := range menus {
		menuResponse := serviceResponse.MenuResponse{}
		if err := utils.ModelToStruct(&menuResponse, &menu); err != nil {
			return nil, err
		}
		menuResponseList = append(menuResponseList, menuResponse)
	}
	menuResponseList = buildMenuTree(menuResponseList)
	return &menuResponseList, nil
}

func (us *UserService) UserLoginService(requestData *serviceRequest.LoginRequest) (*serviceResponse.LoginResponse, *response.ServiceError) {
	var loginResponse serviceResponse.LoginResponse
	var captchaRequest = serviceRequest.VerifyCaptchaRequest{
		ID:     requestData.ID,
		Answer: requestData.Answer,
	}
	verify, err := us.CaptchaService.VerifyCaptchaService(&captchaRequest)
	if err != nil {
		return nil, err
	}
	if !verify {
		return nil, response.WrapError(response.CaptchaInvalidOrExpiredCode, response.GetStatusCode(response.CaptchaInvalidOrExpiredCode).Message)
	}
	condition := map[string]interface{}{
		"account": requestData.Account,
	}
	user, err := us.Repository.GenericQueryDetailByContition(condition, userNotFoundHandler)
	if err != nil {
		return nil, err
	}
	if !utils.VerifyPasswordWithHashedPassword(user.Password, requestData.Password) {
		return nil, response.WrapError(
			response.UserAccountOrPasswordFailedCode,
			response.GetStatusCode(response.UserAccountOrPasswordFailedCode).Message,
		)
	}
	expiresAt, accessToken, tokenErr := utils.GenerateToken(user.UserID, user.IsAdmin)
	if tokenErr != nil {
		return nil, response.WrapError(
			response.UserLoginFailedCode,
			response.GetStatusCode(response.UserLoginFailedCode).Message,
		)
	}
	refresToken, refresTokenErr := utils.RefreshToken(accessToken)
	if refresTokenErr != nil {
		return nil, response.WrapError(
			response.UserLoginFailedCode,
			response.GetStatusCode(response.UserLoginFailedCode).Message,
		)
	}
	redisUtils := &utils.RedisUtils{}
	if err := redisUtils.Set(ctx, user.UserID, accessToken, global.GA_CONFIG.Redis.ExpiresAt*time.Second); err != nil {
		return nil, response.WrapError(
			response.UserLoginFailedCode,
			response.GetStatusCode(response.UserLoginFailedCode).Message,
		)
	}
	loginResponse = serviceResponse.LoginResponse{
		AccessToken:  accessToken,
		RefreshToken: refresToken,
		ExpiresAt:    expiresAt,
	}
	return &loginResponse, nil
}

func buildMenuTree(menuResponseList []serviceResponse.MenuResponse) []serviceResponse.MenuResponse {
	menuMap := make(map[string][]serviceResponse.MenuResponse)
	for _, menuResponse := range menuResponseList {
		menuMap[menuResponse.ParentID] = append(menuMap[menuResponse.ParentID], menuResponse)
	}
	var buildMenuTree func(parentId string) []serviceResponse.MenuResponse
	buildMenuTree = func(parentId string) []serviceResponse.MenuResponse {
		children := menuMap[parentId]
		for i := range children {
			children[i].Children = buildMenuTree(children[i].ID)
		}
		return children
	}
	return buildMenuTree("")
}
